//
//  EncrypterAsyncSocket.swift
//  VPai
//
//  Created by devel on 16/10/26.
//  Copyright © 2016年 viatech. All rights reserved.
//

import Foundation
import tcp2usb

/*
 一个封装了AsyncSocket和Encrypter库的类
 */
class EncrypterAsyncSocket : NSObject, AsyncSocketDelegate {
    
    var event = WebSocketEvents()
    
    override init() {
        super.init()
        _socket.setRunLoopModes([NSRunLoopCommonModes])
        _socket.setDelegate(self)
    }
    
    //建立连接
    func connect(host: String, port: UInt16){
        if _socket.isConnected() {
            _socket.disconnect()
        }
        do{
            try _socket.connectToHost(host, onPort: port, withTimeout: 5)
        }catch{
            Log.error(EncrypterAsyncSocket.TAG, msg: "socket connect error:\(error)")
            event.error(error: error)
        }
    }
    
    //断开连接
    func disconnect(){
        _socket.disconnect()
    }
    
    //发送消息，msg:要发送的消息 encrypt:是否要加密 cookie:一个Int,一般递增就可以，用于writeDone回调区分哪个消息
    func sendData(msg: String, encrypt: Bool, cookie: Int) {
        if DEBUG {
            Log.info(EncrypterAsyncSocket.TAG, msg: "sendData:\(msg)")
        }
        let msgData : NSData
        
        // 加密以及解密用
       // if encrypt {
            
      //     msgData = Encrypter.encrypt(msg)
//        }else {
//            //msgData = NSData.init(data: <#T##NSData#>)//msg.asData()
//        }
        
        let length : UInt32 = 23//UInt32(msgData.length)
        var head : [UInt8] = []
        for byte in HEAD {
            head.append(byte)
        }
        head.append(UInt8((length & 0xFF000000) >> 24))
        head.append(UInt8((length & 0x00FF0000) >> 16))
        head.append(UInt8((length & 0x0000FF00) >> 8))
        head.append(UInt8((length & 0x000000FF) >> 0))
        let data = NSMutableData()
        data.appendBytes(&head, length: head.count)
        //data.appendData(msgData)
        _socket.writeData(data, withTimeout: writeTimeOut, tag: cookie)
    }
    
    /**
     * Called when a socket connects and is ready for reading and writing.
     * The host parameter will be an IP address, not a DNS name.
     **/
    func onSocket(sock: AsyncSocket, didConnectToHost host: String, port: UInt16) {
        if DEBUG {
            Log.info(EncrypterAsyncSocket.TAG, msg: "didConnectToHost:\(host):\(port)")
        }
        event.open()
        startToReceiveHead()
    }
    
    /**
     * Called when a socket disconnects with or without error.  If you want to release a socket after it disconnects,
     * do so here. It is not safe to do that during "onSocket:willDisconnectWithError:".
     *
     * If you call the disconnect method, and the socket wasn't already disconnected,
     * this delegate method will be called before the disconnect method returns.
     **/
    func onSocketDidDisconnect(sock: AsyncSocket){
        if DEBUG {
            Log.info(EncrypterAsyncSocket.TAG, msg: "onSocketDidDisconnect")
        }
        event.close()
    }
    
    /**
     * Called when a socket has completed writing the requested data. Not called if there is an error.
     **/
    func onSocket(sock: AsyncSocket, didWriteDataWithTag tag: Int) {
        if DEBUG {
            Log.info(EncrypterAsyncSocket.TAG, msg: "didWriteDataWithTag:tag = \(tag)")
        }
        event.writeDone(success: true, cookie: tag)
    }
    
    /**
     * Called when a socket has completed reading the requested data into memory.
     * Not called if there is an error.
     **/
    func onSocket(sock: AsyncSocket, didReadData data: NSData, withTag tag: Int) {
        if DEBUG {
            Log.info(EncrypterAsyncSocket.TAG, msg: "didReadData: tag = \(tag)")
        }
        if let readtag = ReadTag(rawValue: tag) {
            var array = [UInt8](count: data.length, repeatedValue: 0)
            data.getBytes(&array, length: data.length)
            switch readtag {
            case ReadTag.HEAD: //数据头
                if Number.isUInt8ArrayEqual(array, array2: HEAD) {
                    startToReceiveBodyLen()
                    return
                }
            case ReadTag.BODY_LEN: //数据长度
                if array.count == 4 {
                    let len = Number.UInt8ToUInt32(array[0], b2: array[1], b3: array[2],
                                            b4: array[3], isLittleEndian: false)
                    startToReceiveBody(Int(len))
                    return
                }
          //  case ReadTag.BODY: //数据
//                //先尝试解码，如果不能解码则是明文
//              //  if let bodyString = Encrypter.decrypt(array) {
//               //     handleReceiveBody(bodyString)
//                }else if let bodyString = NSString(data: data, encoding: NSUTF8StringEncoding) as? String{
//                    handleReceiveBody(bodyString)
//                }
            default: break
                
            }
        }
        startToReceiveHead()
    }
    
    /**
     * Called if a read operation has reached its timeout without completing.
     * This method allows you to optionally extend the timeout.
     * If you return a positive time interval (> 0) the read's timeout will be extended by the given amount.
     * If you don't implement this method, or return a non-positive time interval (<= 0) the read will timeout as usual.
     *
     * The elapsed parameter is the sum of the original timeout, plus any additions previously added via this method.
     * The length parameter is the number of bytes that have been read so far for the read operation.
     *
     * Note that this method may be called multiple times for a single read if you return positive numbers.
     **/
    func onSocket(sock: AsyncSocket, shouldTimeoutReadWithTag tag: Int, elapsed: NSTimeInterval, bytesDone length: UInt) -> NSTimeInterval {
        if DEBUG {
            Log.error(EncrypterAsyncSocket.TAG, msg: "shouldTimeoutReadWithTag: tag = \(tag), elapsed = \(elapsed), length = \(length)")
        }
        event.error(error: EncrypterAsyncSocketError.ReadTimeOut)
        return 0
    }
    
    /**
     * Called if a write operation has reached its timeout without completing.
     * This method allows you to optionally extend the timeout.
     * If you return a positive time interval (> 0) the write's timeout will be extended by the given amount.
     * If you don't implement this method, or return a non-positive time interval (<= 0) the write will timeout as usual.
     *
     * The elapsed parameter is the sum of the original timeout, plus any additions previously added via this method.
     * The length parameter is the number of bytes that have been written so far for the write operation.
     *
     * Note that this method may be called multiple times for a single write if you return positive numbers.
     **/
    func onSocket(sock: AsyncSocket, shouldTimeoutWriteWithTag tag: Int, elapsed: NSTimeInterval, bytesDone length: UInt) -> NSTimeInterval {
        if DEBUG {
            Log.error(EncrypterAsyncSocket.TAG, msg: "shouldTimeoutWriteWithTag: tag = \(tag), elapsed = \(elapsed), length = \(length)")
        }
        event.writeDone(success: false, cookie: tag)
        return 0
    }
    
    private static let TAG = "EncrypterAsyncSocket"
    private let DEBUG = true
    private let _socket : AsyncSocket = AsyncSocket()
    private let HEAD : [UInt8] = [0x50, 0x3A] //head is "P:"
    private let readTimeOut : NSTimeInterval = 10
    private let writeTimeOut : NSTimeInterval = 20
    
    //开始接收头,不设置超时，一直读到为止
    private func startToReceiveHead() {
        _socket.readDataToLength(UInt(HEAD.count), withTimeout: -1, buffer: nil,
                                 bufferOffset: 0, tag: ReadTag.HEAD.rawValue)
    }
    
    //开始接收数据长度,不设置超时，一直读到为止
    private func startToReceiveBodyLen() {
        _socket.readDataToLength(4, withTimeout: -1, buffer: nil,
                                 bufferOffset: 0, tag: ReadTag.BODY_LEN.rawValue)
    }
    
    //开始接收数据，并设置readTimeOut超时时间
    private func startToReceiveBody(len: Int) {
        _socket.readDataToLength(UInt(len), withTimeout: readTimeOut, buffer: nil,
                                 bufferOffset: 0, tag: ReadTag.BODY.rawValue)
    }
    
    private func handleReceiveBody(body: String) {
        event.message(data: body)
    }
    
    
}

struct WebSocketEvents {
    var open : ()->() = {} //socket建立连接成功
    var close : ()->() = {} //socket断开连接
    var error : (error : ErrorType)->() = {(error) in} //发生错误
    var message : (data : Any)->() = {(data) in} //socket收到信息
    var writeDone : (success: Bool, cookie: Int) -> () = {success, cookie in} //发送消息回调
}

enum EncrypterAsyncSocketError: ErrorType {
    case ReadTimeOut/*读取数据超时*/
}

private enum ReadTag: Int{
    case UNKNOW = 0, HEAD = 1, BODY_LEN = 2, BODY = 3
}